<?php
def("amicitia/modelo/entidades/","Persona");
def("amicitia/modelo/entidades/","Pareja");
def("amicitia/modelo/entidades/","Juego");
def("amicitia/modelo/ejecucion/","Verificador");
def("amicitia/modelo/ejecucion/","Nodo");
def("amicitia/modelo/excepciones/","IntegridadDeJuegoException");

/**
 * Cupido se encarga de emparejar a los amigos. Anazila todas las posibilidades
 * construyendo un árbol de parejas e identificando y evitando los deadlocks
 * garantizando así la convergencia del juego.
 * 
 * @author Willy A. Kuster
 */
class Cupido {
	
	private $juego;
	private $verificador;
	private $rama = array();
	private $interesado;
	
	private $noregalados;
	private $noregalantes;
	
	private $convergencias = 0;
	private $deadlocks = 0;
	
	private $permutaciones = 0;
	
	private $probabilidadConjunta = 0;
	
	public function __construct(Verificador $verificador, Juego $juego) {
		$this->juego = $juego;
		$this->verificador = $verificador;
		$this->verificador->setCupido($this);
		$this->verificador->setReglas($this->juego->getReglas());
	}
		
	public function emparejar(Persona $persona) {
				
		if(count($this->juego->getJugadores())<2)
			throw new IntegridadDeJuegoException($this->juego,$persona,"Un Juego debe tener al menos dos jugadores.");
		
		if(in_array($persona,$this->juego->getRegalantes()))
			throw new IntegridadDeJuegoException($this->juego,$persona,"El jugador ya fue emparejado.");
		
		$noregalados = array_udiff($this->juego->getJugadores(),$this->juego->getRegalados(), array("Cupido", "compararPersonas"));
		$this->noregalados = $noregalados;
		$noregalantes = array_udiff($this->juego->getJugadores(),$this->juego->getRegalantes(), array("Cupido", "compararPersonas"));
		$this->noregalantes = $noregalantes;
		
		if(count($noregalados)!=count($noregalantes))
			throw new IntegridadDeJuegoException($this->juego,$persona,"La cantidad de regalantes debe ser igual a la de regalados.");
			
		if(count($noregalados)==0 || count($noregalantes)==0)
			throw new IntegridadDeJuegoException($this->juego,$persona,"Todos los jugadores ya tienen pareja asignada.");
			
		$this->interesado = $persona;
					
		$posibilidadesPonderadas = array();
		
		$this->eliminarElemento($noregalados,$persona);
		
		$this->permutaciones = 0;
		
		foreach($noregalados as $posibleRegalado) {
			$posiblePareja = new Pareja();
			$posiblePareja->setDador($persona);
			$posiblePareja->setReceptor($posibleRegalado);
			
			$this->convergencias = 0;
			$this->deadlocks = 0;
			$indiceConvergencia = 0;
			
			if ($this->verificador->aplicarReglas($posiblePareja)){
				$this->ramificar($posiblePareja);
				$indiceConvergencia = ($this->convergencias+$this->deadlocks)==0 ? 0 : $this->convergencias/($this->convergencias+$this->deadlocks);
				$this->permutaciones += $this->convergencias;
			}
			
			array_push($posibilidadesPonderadas,array("indice" => $indiceConvergencia,"pareja" => $posiblePareja));
		}
		
		$this->interesado = null;
		
		$pareja = $this->determinarPareja($posibilidadesPonderadas);
		
		if ($pareja==null) return;
		
		$regalados = $this->juego->getRegalados();
		$regalados[$pareja->getReceptor()->getId()] = $pareja->getReceptor();
		$this->juego->setRegalados($regalados);
		
		$regalantes = $this->juego->getRegalantes();
		$regalantes[$pareja->getDador()->getId()] = $pareja->getDador();
		$this->juego->setRegalantes($regalantes);
		
		return $pareja;
		
	}
	
	public function estadisticas() {
		
		$regalantes = $this->juego->getRegalantes();
		$regalados = $this->juego->getRegalados();
		
		$probabilidadTotal = 0;
		
		foreach($this->juego->getJugadores() as $jugador){
			$this->juego->setRegalantes(array());
			$this->juego->setRegalados(array());
			$this->emparejar($jugador);
			if($this->probabilidadConjunta == 0) return array("convergencia" => 0, "permutaciones" => 0);
			$probabilidadTotal += $this->probabilidadConjunta;
		}
		
		$permutaciones = $this->permutaciones;
		
		$probabilidadTotal = count($this->juego->getJugadores())!=0 ? ($probabilidadTotal)/count($this->juego->getJugadores()) : 0;
		
		$this->juego->setRegalantes($regalantes);
		$this->juego->setRegalados($regalados);
		
		return array("convergencia" => $probabilidadTotal, "permutaciones" => $permutaciones);
		
	}
	
	public function getJuego() {
		return $this->juego;
	}
	
	public function getRama() {
		return $this->rama;
	}
	
	public function getInteresado() {
		return $this->interesado;
	}
	
	private function ramificar(Pareja $posiblePareja){
		
		array_push($this->rama,$posiblePareja);
				
		$this->eliminarElemento($this->noregalados,$posiblePareja->getReceptor());
		$this->eliminarElemento($this->noregalantes,$posiblePareja->getDador());
		
		$posibilidades = $this->generarPosibilidades($this->noregalados,$this->noregalantes);
		
		foreach($posibilidades as $posibilidad){
			$this->ramificar($posibilidad);
		}
		
		if (count($posibilidades)==0){
			if(count($this->noregalados)==0 && count($this->noregalantes)==0)
				$this->convergencias++;
			else
				$this->deadlocks++;
		}
		
		$this->noregalados[$posiblePareja->getReceptor()->getId()] = $posiblePareja->getReceptor();
		$this->noregalantes[$posiblePareja->getDador()->getId()] = $posiblePareja->getDador();
		
		$this->eliminarElemento($this->rama,$posiblePareja);
				
	}
	
	private function generarPosibilidades(array $regalados, array $regalantes) {
		
		$posibilidades = array();
		
		foreach($regalantes as $dador) {
			foreach($regalados as $receptor) {
				if($dador!=$receptor){
					
					$posiblePareja = new Pareja();
					$posiblePareja->setDador($dador);
					$posiblePareja->setReceptor($receptor);
					if ($this->verificador->aplicarReglas($posiblePareja))
						array_push($posibilidades,$posiblePareja);
						
				}
			}
		}
		
		return $posibilidades;
		
	}

	private function eliminarElemento(array &$arreglo, $elemento) {
		$keys = array_keys($arreglo,$elemento);
		foreach($keys as $key) {
	        unset($arreglo[$key]);
	    }
	    return $arreglo;
	}
	
	private function determinarPareja(array $posibilidadesPonderadas){
		
		shuffle($posibilidadesPonderadas);
		
		$this->probabilidadConjunta = 0;
		
		$pareja = null;
		$indiceMayor = 0;
		foreach($posibilidadesPonderadas as $posibilidadPonderada){
			if($posibilidadPonderada["indice"]>$indiceMayor){
				$indiceMayor = $posibilidadPonderada["indice"];
				$pareja = $posibilidadPonderada["pareja"];
			}
			$this->probabilidadConjunta += $posibilidadPonderada["indice"];
		}
		
		$this->probabilidadConjunta = count($posibilidadesPonderadas)!=0 ? ($this->probabilidadConjunta)/count($posibilidadesPonderadas) : 0;
		
		return $pareja;
	}
	
	public static function compararPersonas($obj1,$obj2){
		if($obj1==$obj2) return 0;
		return ($obj1->getId() > $obj2->getId())? 1:-1;
	}
	
}
?>